package com.eece411.project.p2;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;



public class Node implements Instance {
	private String host;
	private int port;
	private int latency;
	public Node(String host,int port, long l){
		this.host=host;
		this.port=port;
		this.latency=(int) l;
		System.out.println("Latency : "+this.latency);
	}
	@Override
	public boolean put(byte[] key, byte[] data, boolean isReplication) throws deadException {
		// Do a socket communication w/ protocol to a remote node
		byte[] inputBytes = null;
		try {
			Socket s = this.getSocket();
			long startTime = System.currentTimeMillis();
			if(!isReplication){
				s.getOutputStream().write((byte)0x01);
				s.getOutputStream().write(key);
				s.getOutputStream().write(data);
				inputBytes = new byte[Routing.DATA_HEADER];
		    	int totalBytesRcvd2 = 0;
		    	while (totalBytesRcvd2 < inputBytes.length && ((System.currentTimeMillis()-startTime)<1500)) {
		    		int bytesRcvd;
		    		if ((bytesRcvd = s.getInputStream().read(inputBytes, totalBytesRcvd2,  
		    				inputBytes.length - totalBytesRcvd2)) == -1)
		                   	throw new SocketException("Connection close prematurely");
		    		 	totalBytesRcvd2 += bytesRcvd;
		    	}
		    	s.close();
				if (((inputBytes[0] & 0xFF) == 0x00)){
					return true;
				} else if (((inputBytes[0] & 0xFF) == 0x05)){
					throw new deadException("Remote Node is overload");
				} else {
					return false;
				}
			} else {
				s.getOutputStream().write((byte)0x21);
				s.getOutputStream().write(key);
				s.getOutputStream().write(data);
				s.close();
				return false;
			}
	
		} catch (Exception e) {
			throw new deadException("Node is dead");
		}

	}

	@Override
	public byte[] get(byte[] key) throws deadException {
		// Do a socket communication w/ protocol to a remote node
		byte[] result = null;
		byte[] inputBytes = null;
		try {
			Socket s = this.getSocket();
			long startTime = System.currentTimeMillis();
			s.getOutputStream().write((byte)0x02);
			s.getOutputStream().write(key);
			result = new byte[Routing.DATA_HEADER];
			inputBytes = new byte[Routing.DATA_LENGHT];
	    	int totalBytesRcvd2 = 0;
	    	while (totalBytesRcvd2 < result.length && ((System.currentTimeMillis()-startTime)<1500)) {
	    		int bytesRcvd;
	    		if ((bytesRcvd = s.getInputStream().read(result, totalBytesRcvd2,  
	    				result.length - totalBytesRcvd2)) == -1)
	                   	throw new SocketException("Connection close prematurely");
	    		 	totalBytesRcvd2 += bytesRcvd;
	    	}
	    	if (((result[0] & 0xFF) == 0x00)){
		    	totalBytesRcvd2 = 0;
		    	while (totalBytesRcvd2 < inputBytes.length && ((System.currentTimeMillis()-startTime)<1000)) {
		    		int bytesRcvd;
		    		if ((bytesRcvd = s.getInputStream().read(inputBytes, totalBytesRcvd2,  
		    				inputBytes.length - totalBytesRcvd2)) == -1)
		                   	throw new SocketException("Connection close prematurely");
		    		 	totalBytesRcvd2 += bytesRcvd;
		    	}
	    	}
	    	s.close();
			if (((result[0] & 0xFF) == 0x00)){
				return inputBytes;
			} else if (((inputBytes[0] & 0xFF) == 0x05)){
				throw new deadException("Remote Node is overload");
			} else {
				return null;
			}
			
		} catch (Exception e) {
			throw new deadException("Node is dead");
		}

	}

	@Override
	public boolean remove(byte[] key,boolean isReplication) throws deadException {
		// Do a socket communication w/ protocol to a remote node
		byte[] inputBytes = null;
		try {
			Socket s = this.getSocket();
			long startTime = System.currentTimeMillis();
			if(!isReplication){
				s.getOutputStream().write((byte)0x03);
				s.getOutputStream().write(key);
				inputBytes = new byte[Routing.DATA_HEADER];
		    	int totalBytesRcvd2 = 0;
		    	while (totalBytesRcvd2 < inputBytes.length && ((System.currentTimeMillis()-startTime)<1500)) {
		    		int bytesRcvd;
		    		if ((bytesRcvd = s.getInputStream().read(inputBytes, totalBytesRcvd2,  
		    				inputBytes.length - totalBytesRcvd2)) == -1)
		                   	throw new SocketException("Connection close prematurely");
		    		 	totalBytesRcvd2 += bytesRcvd;
		    	}
		    	s.close();
				if (((inputBytes[0] & 0xFF) == 0x00)){
					return true;
				} else if (((inputBytes[0] & 0xFF) == 0x05)){
					throw new deadException("Remote Node is overload");
				} else {
					return false;
				}
			} else {
				s.getOutputStream().write((byte)0x23);
				s.getOutputStream().write(key);
				s.close();
				return true;
			}
		} catch (Exception e) {
			throw new deadException("Node is dead");
		}

	}

	@Override
	public boolean containsKey(byte[] key) throws deadException {
		// Do a socket communication w/ protocol to a remote node
		byte[] inputBytes = null;
		try {
			Socket s = this.getSocket();
			s.getOutputStream().write((byte)0x20);
			s.getOutputStream().write(key);
			inputBytes = new byte[Routing.DATA_HEADER];
	    	int totalBytesRcvd2 = 0;
	    	while (totalBytesRcvd2 < inputBytes.length) {
	    		int bytesRcvd;
	    		if ((bytesRcvd = s.getInputStream().read(inputBytes, totalBytesRcvd2,  
	    				inputBytes.length - totalBytesRcvd2)) == -1)
	                   	throw new SocketException("Connection close prematurely");
	    		 	totalBytesRcvd2 += bytesRcvd;
	    	}
			s.close();
		} catch (Exception e) {
			throw new deadException("Node is dead");
		}
		if (((inputBytes[0] & 0xFF) == 0x00)){
			return true;
		} else if (((inputBytes[0] & 0xFF) == 0x05)){
			throw new deadException("Remote Node is overload");
		} else {
			return false;
		}
	}

	private Socket getSocket() throws UnknownHostException, IOException{
		return new Socket(host,port);
	}
	
	@Override
	public int getLatency() {
		return this.latency;
	}
	@Override
	public int compareTo(Instance instance) {
		return instance.getLatency()-this.getLatency();
	}
	@Override
	public boolean equals(String s) {
		return host.equals(s);
	}
}
